package Utils;

import Utils.Ficheros.FicheroDatos;
import java.awt.Graphics;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.awt.Image;
import java.awt.Toolkit;
import java.net.HttpURLConnection;
import java.net.URL;
//import javax.microedition.io.Connector;

/**
 * Clase contenedora de metodos de diversa utilidad.
 */
public class Util {

    private static double offset = 268435456;
    private static double radius = offset / Math.PI;
    private static double PI = 3.14159265;
    private static double FOURTHPI = PI / 4;
    private static double deg2rad = PI / 180;
    private static double rad2deg = 180.0 / PI;
    public static Ellipsoid ellipsoid[] = {//  id, Ellipsoid name, Equatorial Radius, square of eccentricity
        new Ellipsoid(0, "Placeholder", 0.0, 0.0),//placeholder only, To allow array indices to match id numbers
        new Ellipsoid(1, "Airy", 6377563, 0.00667054),
        new Ellipsoid(2, "Australian National", 6378160, 0.006694542),
        new Ellipsoid(3, "Bessel 1841", 6377397, 0.006674372),
        new Ellipsoid(4, "Bessel 1841 (Nambia) ", 6377484, 0.006674372),
        new Ellipsoid(5, "Clarke 1866", 6378206, 0.006768658),
        new Ellipsoid(6, "Clarke 1880", 6378249, 0.006803511),
        new Ellipsoid(7, "Everest", 6377276, 0.006637847),
        new Ellipsoid(8, "Fischer 1960 (Mercury) ", 6378166, 0.006693422),
        new Ellipsoid(9, "Fischer 1968", 6378150, 0.006693422),
        new Ellipsoid(10, "GRS 1967", 6378160, 0.006694605),
        new Ellipsoid(11, "GRS 1980", 6378137, 0.00669438),
        new Ellipsoid(12, "Helmert 1906", 6378200, 0.006693422),
        new Ellipsoid(13, "Hough", 6378270, 0.00672267),
        new Ellipsoid(14, "International", 6378388, 0.00672267),
        new Ellipsoid(15, "Krassovsky", 6378245, 0.006693422),
        new Ellipsoid(16, "Modified Airy", 6377340, 0.00667054),
        new Ellipsoid(17, "Modified Everest", 6377304, 0.006637847),
        new Ellipsoid(18, "Modified Fischer 1960", 6378155, 0.006693422),
        new Ellipsoid(19, "South American 1969", 6378160, 0.006694542),
        new Ellipsoid(20, "WGS 60", 6378165, 0.006693422),
        new Ellipsoid(21, "WGS 66", 6378145, 0.006694542),
        new Ellipsoid(22, "WGS 72", 6378135, 0.006694318),
        new Ellipsoid(23, "WGS 84", 6378137, 0.00669438),
        new Ellipsoid(24, "European 1950", 6378388, 0.00672267)
    };

    private byte[] intToByteArray(final int integer) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeInt(integer);
        dos.flush();
        return bos.toByteArray();
    }

    /*--------------------------------------------------
     * Open connection and download png into a byte array.
     *-------------------------------------------------*/
    public static Image getImage(String u) throws IOException {
        URL url = new URL(u);
        HttpURLConnection hc = (HttpURLConnection) url.openConnection();
        hc.setRequestMethod("GET");
        InputStream iStrm = (InputStream) hc.getInputStream();
        Image im = null;
        try {
            ByteArrayOutputStream bStrm = new ByteArrayOutputStream();
            int ch;
            while ((ch = iStrm.read()) != -1) {
                bStrm.write(ch);
            }
            // Place into image array
            byte imageData[] = bStrm.toByteArray();
            // Create the image from the byte array
            im = Toolkit.getDefaultToolkit().createImage(imageData, 0, imageData.length);
        } finally {
            // Clean up
            if (iStrm != null) {
                iStrm.close();
            }
        }
        return (im == null ? null : im);
    }

    /**
     * Ley de los cosenos: Calcula la distancia en km entre dos puntos especificados
     * por su latitud y longitud
     * @param infoLinea
     * @param infoLineaAntiguo
     * @return
     */
    public static double distCosineLaw(FicheroDatos infoLinea, FicheroDatos infoLineaAntiguo) {
        int R = 6371; // Radio medio de la tierra en kilometros
        double lat1 = infoLinea.getLatitud();
        double lon1 = infoLinea.getLongitud();
        double lat2 = infoLineaAntiguo.getLatitud();
        double lon2 = infoLineaAntiguo.getLongitud();
        double d = mMath.acos(Math.sin(toRad(lat1)) * Math.sin(toRad(lat2)) +
                Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(toRad(lon2 - lon1))) * R;
        return d;
    }

    /**
     * 
     * @param infoLinea
     * @param infoLineaAntiguo
     * @return
     */
    public static double distCosineLaw(double lat1, double lon1, double lat2, double lon2) {
        int R = 6371; // Radio medio de la tierra en kilometros
        double d = mMath.acos(Math.sin(toRad(lat1)) * Math.sin(toRad(lat2)) +
                Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.cos(toRad(lon2 - lon1))) * R;
        return d;
    }

    /**
     * Convierte un parametro de tipo double a radianes
     * @param dato parametro de entrada (longitud o latitud)
     * @return numero transformado a radianes
     */
    public static double toRad(double dato) {
        double rad = dato * Math.PI / 180;
        return rad;
    }

    /**
     * Metodo que da la velocidad de un objeto sabiendo el espacio recorrido
     * en un determinado lapso de tiempo
     * @param dist Distancia recorrida
     * @param tiempo Lapso de tiempo transcurrido
     * @return Velocidad del cuerpo en m/s
     */
    public static double velocidad(double dist, int tiempo) {
        double vel;
        //velocidad = espacio / tiempo
        vel = (dist * 1000) / tiempo;
        return vel;
    }

    /**
     * Crea un estring que representa un numero Double con un formato de un 
     * numero determinado de decimales. 
     * 
     * @param valor-
     *            valor en formato Double a ser convertido.
     * @param decimales -
     *            numero de decimales para el String.
     * @return una representacion del numero en forma de String.
     */
    public static String formatDouble(double valor, int decimales) {
        String doubleStr = "" + valor;
        int index = doubleStr.indexOf(".") != -1 ? doubleStr.indexOf(".")
                : doubleStr.indexOf(",");
        // Decimal point can not be found...
        if (index == -1) {
            return doubleStr;
        }
        // Truncate all decimals
        if (decimales == 0) {
            return doubleStr.substring(0, index);
        }

        int len = index + decimales + 1;
        if (len >= doubleStr.length()) {
            len = doubleStr.length();
        }

        double d = Double.parseDouble(doubleStr.substring(0, len));
        return String.valueOf(d);
    }

    /**
     * Metodo que permite escalar una imagen a las dimensiones requeridas.
     * @param imgOldImage imagen que queremos escalar
     * @param iNewWidth ancho de la imagen que deseamos
     * @param iNewHeight largo de la imagen que deseamos
     * @return la imagen escalada
     */
    public static Image CreateScaledImage(Image imgOldImage, int iNewWidth, int iNewHeight) {
        Image imgNewImage = null;
        final int iOldWidth = imgOldImage.getWidth(null);
        final int iOldHeight = imgOldImage.getHeight(null);

        int iOldRGBArray[] = new int[iOldWidth * iOldHeight];

        //imgOldImage.getRGB(iOldRGBArray, 0, iOldWidth, 0, 0, iOldWidth, iOldHeight);

        int iNewRGBArray[] = new int[iNewWidth * iNewHeight];

        for (int yy = 0; yy < iNewHeight; yy++) {
            int dy = yy * iOldHeight / iNewHeight;

            for (int xx = 0; xx < iNewWidth; xx++) {
                int dx = xx * iOldWidth / iNewWidth;

                iNewRGBArray[(iNewWidth * yy) + xx] = iOldRGBArray[(iOldWidth * dy) + dx];
            }
        }
        //imgNewImage = Image.createRGBImage(iNewRGBArray, iNewWidth, iNewHeight, true);

        return imgNewImage;
    }

    public static int diferenciaMilisegundos(String nuevo, String antiguo) {
        // Variable que captura la longitud de los strings de entrada
        int lon;
        // Tiempo en milisegundos que separa las dos fechas que se analizan
        int diferencia = 0;
        // String donde guardamos los datos parciales de horas, minutos...
        String digitos;
        // Variable para calculos internos de resta de valores
        int resta;

        /* Ajustamos el formato de las horas al deseado (2 digitos siempre 
         * para las horas, minutos y segundos.
         */
        if (nuevo.length() < 8) {
            nuevo = "0" + nuevo;
        }
        if (antiguo.length() < 8) {
            antiguo = "0" + antiguo;
        }

        digitos = antiguo.substring(0, 2);
        int horas1 = Integer.parseInt(digitos);

        digitos = "";
        digitos = nuevo.substring(0, 2);
        int horas2 = Integer.parseInt(digitos);
        resta = horas2 - horas1;

        if (resta > 0) {
            diferencia = resta * 24 * 60 * 1000;
        } else {
            if (resta < 0) {
                diferencia = (horas1 - horas2) * 24 * 60 * 1000;
            }
        }

        digitos = "";
        digitos = antiguo.substring(3, 5);
        int minutos1 = Integer.parseInt(digitos);

        digitos = "";
        digitos = nuevo.substring(3, 5);
        int minutos2 = Integer.parseInt(digitos);
        resta = minutos2 - minutos1;

        if (resta > 0) {
            diferencia = diferencia + resta * 60 * 1000;
        } else {
            if (resta < 0) {
                diferencia = diferencia - (minutos1 - minutos1) * 60 * 1000;
            }
        }

        digitos = "";
        lon = antiguo.length();
        digitos = antiguo.substring(lon - 2);
        int segundos1 = Integer.parseInt(digitos);

        digitos = "";
        lon = nuevo.length();
        digitos = nuevo.substring(lon - 2);
        int segundos2 = Integer.parseInt(digitos);
        resta = segundos2 - segundos1;

        if (resta < 0) {
            diferencia = diferencia - (segundos1 - segundos2) * 1000;
        } else {
            if (resta > 0) {
                diferencia = diferencia + resta * 1000;
            }
        }
        return diferencia;
    }

    /**
     * Método para resolver potencias
     * @param arg argumento de tipo entero
     * @param times exponente
     * @return arg elevado a times
     */
    public static int pow(int arg, int times) {
        int ret = 1;
        for (int i = 1; i <= times; i++) {
            ret = ret * arg;
        }
        return ret;
    }

    /**
     * Método que redondea un double a un numero de cifras decimales
     * @param arg argumento de tipo double
     * @param places numero de cifras decimales
     * @return dato de tipo double redondeado
     */
    public static double round(double arg, int places) {
        double tmp = (double) arg * (pow(10, places));
        int tmp1 = (int) Math.floor(tmp + 0.5);
        double tmp2 = (double) tmp1 / (pow(10, places));
        return tmp2;
    }

    public static CoordUTM LLtoUTM(int ReferenceEllipsoid, double Lat, double Long) {
//converts lat/long to UTM coords.  Equations from USGS Bulletin 1532
//East Longitudes are positive, West longitudes are negative.
//North latitudes are positive, South latitudes are negative
//Lat and Long are in decimal degrees
        //Written by Chuck Gantz- chuck.gantz@globalstar.com
        double UTMNorthing;
        double UTMEasting;
        String UTMZone;
        double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius;
        double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared;
        double k0 = 0.9996;

        double LongOrigin;
        double eccPrimeSquared;
        double N, T, C, A, M;

//Make sure the longitude is between -180.00 .. 179.9
        double LongTemp = (Long + 180) - (int) ((Long + 180) / 360) * 360 - 180; // -180.00 .. 179.9;

        double LatRad = Lat * deg2rad;
        double LongRad = LongTemp * deg2rad;
        double LongOriginRad;
        int ZoneNumber;

        ZoneNumber = (int) ((LongTemp + 180) / 6) + 1;

        if (Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0) {
            ZoneNumber = 32;
        }

        // Special zones for Svalbard
        if (Lat >= 72.0 && Lat < 84.0) {
            if (LongTemp >= 0.0 && LongTemp < 9.0) {
                ZoneNumber = 31;
            } else if (LongTemp >= 9.0 && LongTemp < 21.0) {
                ZoneNumber = 33;
            } else if (LongTemp >= 21.0 && LongTemp < 33.0) {
                ZoneNumber = 35;
            } else if (LongTemp >= 33.0 && LongTemp < 42.0) {
                ZoneNumber = 37;
            }
        }
        LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3;  //+3 puts origin in middle of zone
        LongOriginRad = LongOrigin * deg2rad;

        //compute the UTM Zone from the latitude and longitude
        UTMZone = "" + (UTMLetterDesignator(Lat));
        //sprintf(UTMZone, "%d%c", ZoneNumber, UTMLetterDesignator(Lat));
        eccPrimeSquared = (eccSquared) / (1 - eccSquared);

        N = a / Math.sqrt(1 - eccSquared * Math.sin(LatRad) * Math.sin(LatRad));
        T = Math.tan(LatRad) * Math.tan(LatRad);
        C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad);
        A = Math.cos(LatRad) * (LongRad - LongOriginRad);

        M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(2 * LatRad) + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(4 * LatRad) - (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.sin(6 * LatRad));

        UTMEasting = (double) (k0 * N * (A + (1 - T + C) * A * A * A / 6 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120) + 500000.0);

        UTMNorthing = (double) (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)));
        if (Lat < 0) {
            UTMNorthing += 10000000.0; //10000000 meter offset for southern hemisphere
        }
        return new CoordUTM(UTMNorthing, UTMEasting, UTMZone);
    }

    static char UTMLetterDesignator(double Lat) {
//This routine determines the correct UTM letter designator for the given latitude
//returns 'Z' if latitude is outside the UTM limits of 84N to 80S
        //Written by Chuck Gantz- chuck.gantz@globalstar.com
        char LetterDesignator;

        if ((84 >= Lat) && (Lat >= 72)) {
            LetterDesignator = 'X';
        } else if ((72 > Lat) && (Lat >= 64)) {
            LetterDesignator = 'W';
        } else if ((64 > Lat) && (Lat >= 56)) {
            LetterDesignator = 'V';
        } else if ((56 > Lat) && (Lat >= 48)) {
            LetterDesignator = 'U';
        } else if ((48 > Lat) && (Lat >= 40)) {
            LetterDesignator = 'T';
        } else if ((40 > Lat) && (Lat >= 32)) {
            LetterDesignator = 'S';
        } else if ((32 > Lat) && (Lat >= 24)) {
            LetterDesignator = 'R';
        } else if ((24 > Lat) && (Lat >= 16)) {
            LetterDesignator = 'Q';
        } else if ((16 > Lat) && (Lat >= 8)) {
            LetterDesignator = 'P';
        } else if ((8 > Lat) && (Lat >= 0)) {
            LetterDesignator = 'N';
        } else if ((0 > Lat) && (Lat >= -8)) {
            LetterDesignator = 'M';
        } else if ((-8 > Lat) && (Lat >= -16)) {
            LetterDesignator = 'L';
        } else if ((-16 > Lat) && (Lat >= -24)) {
            LetterDesignator = 'K';
        } else if ((-24 > Lat) && (Lat >= -32)) {
            LetterDesignator = 'J';
        } else if ((-32 > Lat) && (Lat >= -40)) {
            LetterDesignator = 'H';
        } else if ((-40 > Lat) && (Lat >= -48)) {
            LetterDesignator = 'G';
        } else if ((-48 > Lat) && (Lat >= -56)) {
            LetterDesignator = 'F';
        } else if ((-56 > Lat) && (Lat >= -64)) {
            LetterDesignator = 'E';
        } else if ((-64 > Lat) && (Lat >= -72)) {
            LetterDesignator = 'D';
        } else if ((-72 > Lat) && (Lat >= -80)) {
            LetterDesignator = 'C';
        } else {
            LetterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits
        }
        return LetterDesignator;
    }

    public double[] UTMtoLL(int ReferenceEllipsoid, double UTMNorthing, double UTMEasting, String UTMZone) {
        //converts UTM coords to lat/long.  Equations from USGS Bulletin 1532
        //East Longitudes are positive, West longitudes are negative.
        //North latitudes are positive, South latitudes are negative
        //Lat and Long are in decimal degrees.
        //Written by Chuck Gantz- chuck.gantz@globalstar.com

        double k0 = 0.9996;
        double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius;
        double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared;
        double eccPrimeSquared;
        double e1 = (1 - Math.sqrt(1 - eccSquared)) / (1 + Math.sqrt(1 - eccSquared));
        double N1, T1, C1, R1, D, M;
        double LongOrigin;
        double mu, phi1, phi1Rad;
        double x, y;
        int ZoneNumber;
        String ZoneLetter;
        int NorthernHemisphere; //1 for northern hemispher, 0 for southern

        x = UTMEasting - 500000.0; //remove 500,000 meter offset for longitude
        y = UTMNorthing;

        //ZoneNumber = strtoul(UTMZone, &  ZoneLetter, 10);
        //if ((*  ZoneLetter - 'N') >= 0) {
        NorthernHemisphere = 1;//point is in northern hemisphere
        //} else {
        //NorthernHemisphere = 0;//point is in southern hemisphere
        //y -= 10000000.0;//remove 10,000,000 meter offset used for southern hemisphere
        //}

        LongOrigin = (32 - 1) * 6 - 180 + 3;  //+3 puts origin in middle of zone

        eccPrimeSquared = (eccSquared) / (1 - eccSquared);

        M = y / k0;
        mu = M / (a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256));

        phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu);
        phi1 = phi1Rad * rad2deg;

        N1 = a / Math.sqrt(1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
        T1 = Math.tan(phi1Rad) * Math.tan(phi1Rad);
        C1 = eccPrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
        double temp = 1 - eccSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad);
        R1 = a * (1 - eccSquared) / Math.sqrt(temp * temp * temp);
        D = x / (N1 * k0);

        double Lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720);
        Lat = Lat * rad2deg;

        double Long = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad);
        Long = LongOrigin + Long * rad2deg;

        double[] coords = new double[2];
        coords[0] = Lat;
        coords[1] = Long;
        return coords;

    }

    public static double[] adjust(double lat, double lng, int deltaX, int deltaY, int z) {
        return new double[]{
                    XToL(LToX(lng) + (deltaX << (21 - z))),
                    YToL(LToY(lat) + (deltaY << (21 - z)))
                };
    }

    public static double LToX(double x) {
        return round(offset + radius * x * Math.PI / 180);
    }

    public static double LToY(double y) {
        return round(
                offset - radius *
                Double.longBitsToDouble(MicroDouble.log(
                Double.doubleToLongBits(
                (1 + Math.sin(y * Math.PI / 180)) /
                (1 - Math.sin(y * Math.PI / 180))))) / 2);
    }

    public static double XToL(double x) {
        return ((round(x) - offset) / radius) * 180 / Math.PI;
    }

    public static double YToL(double y) {
        return (Math.PI / 2 - 2 * Double.longBitsToDouble(
                MicroDouble.atan(
                MicroDouble.exp(Double.doubleToLongBits((round(y) - offset) / radius))))) * 180 / Math.PI;
    }

    static double round(double num) {
        double floor = Math.floor(num);

        if (num - floor >= 0.5) {
            return Math.ceil(num);
        } else {
            return floor;
        }
    }
}
